/************************************************************************/
/*    Author: Scott Bevin                                               */
/************************************************************************/

#include "OrbitalCamera.h"

#include "../XDebugging/DebugVariableTracker.h"
#include "../../XUtilities/Utilities.h"

namespace XF
{
	OrbitalCamera::OrbitalCamera(XF::OgreRenderer *renderer)
	{
		mRenderer = renderer;

		mCamera = mRenderer->GetDefaultCamera();

		mName = "OrbitalCamera: " + XF::Utilities::CreateRandomString();

		mTargetNode = mRenderer->GetDefaultSceneManager()->getRootSceneNode()->createChildSceneNode(mName + "_target");
		mCameraNode = mTargetNode->createChildSceneNode(mName);

		mCameraNode->setAutoTracking(true, mTargetNode);

		mCamera->detatchFromParent();
		mCameraNode->attachObject(mCamera);

		mTightness = 0.8f;

		mDistance = mDesiredDistance = 200.0f;

		mYaw = mDesiredYaw = 0.0f;
		mPitch = mDesiredPitch = 0.0f;
		mRoll = mDesiredRoll = 0.0f;

		mMaxDist = MathHelper::Infinity();
		mMinDist = -MathHelper::Infinity();

		mDesiredTargetPos = mTargetNode->getPosition();
	}

	OrbitalCamera::~OrbitalCamera()
	{
		mCameraNode->detachAllObjects();

		mRenderer->GetDefaultSceneManager()->destroySceneNode(mName);
		mRenderer->GetDefaultSceneManager()->destroySceneNode(mName + "_target");
	}

	void OrbitalCamera::Update( double dT )
	{
		mTargetNode->yaw(Ogre::Radian(1.0f * dT));

		UpdatePosition(dT);

		UpdateOrientation(dT);

		UpdateDistance(dT);
	}

	void OrbitalCamera::UpdatePosition( double dT )
	{
		XF::Vector3 displacement;
		XF::Vector3 pos = mTargetNode->getPosition();

		displacement = (mDesiredTargetPos - pos) * mTightness;
		mTargetNode->translate(displacement * dT);
	}

	void OrbitalCamera::UpdateOrientation( double dT )
	{
		mYaw =		XF::MathHelper::Lerp(mYaw, mDesiredYaw, mTightness * dT);
		mPitch =	XF::MathHelper::Lerp(mPitch, mDesiredPitch, mTightness * dT);
		mRoll =		XF::MathHelper::Lerp(mRoll, mDesiredRoll, mTightness * dT);

		Ogre::Matrix3 mat;
		mat.FromEulerAnglesYXZ(Ogre::Radian(mYaw), Ogre::Radian(mPitch) ,Ogre::Radian(0.0f));
		Ogre::Quaternion targetOrientation;
		targetOrientation.FromRotationMatrix(mat);

		mat.FromEulerAnglesXYZ(Ogre::Radian(0.0f), Ogre::Radian(0.0f), Ogre::Radian(mRoll));
		Ogre::Quaternion camOrientation;
		camOrientation.FromRotationMatrix(mat);

		//mCameraNode->setOrientation(camOrientation);
		mTargetNode->setOrientation(targetOrientation);
	}

	void OrbitalCamera::UpdateDistance( double dT )
	{
		mDistance = XF::MathHelper::Lerp(mDistance, mDesiredDistance, mTightness * dT);
		XF::Vector3 vec = (mCameraNode->getPosition() - mTargetNode->getPosition()).normalisedCopy() * mDistance;

		MathHelper::Clamp(mDistance, mMinDist, mMaxDist);

		mCameraNode->setPosition(XF::Vector3(0.0f, 0.0f, mDistance));
	}

	void OrbitalCamera::Yaw( float val, bool instantUpdate /*= false*/ )
	{
		mDesiredYaw = val;

		if(instantUpdate)
		{
			mYaw = val;
		}
	}

	void OrbitalCamera::Pitch( float val, bool instantUpdate /*= false*/ )
	{
		mDesiredPitch = val;

		if(instantUpdate)
		{
			mPitch = val;
		}
	}

	void OrbitalCamera::Roll( float val, bool instantUpdate /*= false*/ )
	{
		mDesiredRoll = val;

		if(instantUpdate)
		{
			mRoll = val;
		}
	}

	void OrbitalCamera::Distance( float val, bool instantUpdate /*= false*/ )
	{
		mDesiredDistance = val;

		if(instantUpdate)
		{
			mDistance = val;
		}
	}

	void OrbitalCamera::FocalPoint( XF::Vector3 val, bool instantUpdate /*= false*/ )
	{
		mDesiredTargetPos = val;

		if(instantUpdate)
		{
			mTargetNode->setPosition(val);
		}
	}

	void OrbitalCamera::AddToFocalPointRelative( XF::Vector3 val, bool instantUpdate /*= false*/ )
	{	
		//mDesiredTargetPos = Vector3::ZERO;	
		XF::Vector3 temp = Vector3::ZERO;
		temp.x = cos(mYaw*RAD)*val.z;
		temp.z = sin(mYaw*RAD)*val.z;
	
		temp.x += cos((mYaw+90)*RAD)*val.x;
		temp.z += sin((mYaw+90)*RAD)*val.x;
		mTargetNode->setPosition(mTargetNode->getPosition()+temp);

		if(instantUpdate)
		{	
			mTargetNode->setPosition(mDesiredTargetPos);
		}
	}

	XF::Vector3 OrbitalCamera::GetLookAtDirection()
	{
		return (mCamera->getRealPosition()-mTargetNode->getPosition()).normalisedCopy();
	}
}